package jobteam.contacteventstocalendar;

import java.util.Calendar;
import java.util.Date;


public class LunaDate implements LunaDateConstants {
	
	// 양/음력 기준일
    private static final Date SOLRA_FIXED_DATE = new Date(1801, 1, 1);
    private static final LunaDate LUNA_FIXED_DATE = new LunaDate(1800, 11, 17);
    
    // 속도 향상을 위한 캐시
    private static final Object[][] CACHE = {
    	{1851, new Date(1851, 1, 1), 1850, new LunaDate(1850, 11, 29)},
    	{1901, new Date(1901, 1, 1), 1900, new LunaDate(1900, 11, 11)},
    	{1951, new Date(1951, 1, 1), 1950, new LunaDate(1950, 11, 24)},
    	{2001, new Date(2001, 1, 1), 2000, new LunaDate(2000, 12,  7)},
    };
    
    private int year;
    private int month;
    private int day;
    private boolean isLeaf;	// 윤달여부
    
    /**
     * <p>음력일 생성자이다.</p>
     * 
     * @param year 음력의 년(1799~2100)
     * @param month 음력의 월(1~12)
     * @param day 음력의 일
     * @param isLeaf 음력의 윤달여부
     */
    public LunaDate(int year, int month, int day) {
    	this.year = year;
    	this.month = month;
    	this.day = day;
    }
    
    public Date toSolarDate() {
    	Date solraFixedDate = null;
    	LunaDate lunaFixedDate = null;
    	for (int i = CACHE.length - 1; i >= 0; i--) {
    		if ((Integer)CACHE[i][2] < year) {
    			solraFixedDate = (Date)CACHE[i][1];
    			lunaFixedDate = (LunaDate)CACHE[i][3];
    			break;
    		}
    	}
    	if (solraFixedDate == null || lunaFixedDate == null) {
			solraFixedDate = SOLRA_FIXED_DATE;
			lunaFixedDate = LUNA_FIXED_DATE;
    	}
    	int amount = lunaFixedDate.getPeriodDays(this);
    	Calendar calendar = Calendar.getInstance();
    	calendar.set(solraFixedDate.getYear(), solraFixedDate.getMonth(), solraFixedDate.getDate());
    	calendar.add(Calendar.DAY_OF_YEAR, amount);
    	return calendar.getTime();
    }
    
    public int compare(LunaDate date) {
		int thisVal = (year * 100000) + (month * 1000) + (day * 10) + (isLeaf ? 1 : 0);
		int anotherVal = (date.year * 100000) + (date.month * 1000) + (date.day * 10) + (date.isLeaf ? 1 : 0);
		return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
	}
    
	public int getPeriodDays(LunaDate date) {
		if (date == null || this.equals(date)) {
			return 0;
		}

		int fromYear, fromMonth, fromDay;
		int toYear, toMonth, toDay;
		boolean fromIsLeaf, toIsLeaf;
		boolean needRevert = false;
		if (compare(date) < 0) {
			fromYear = this.year;
			fromMonth = this.month;
			fromDay = this.day;
			fromIsLeaf = this.isLeaf;
			toYear = date.year;
			toMonth = date.month;
			toDay = date.day;
			toIsLeaf = date.isLeaf;
			
			needRevert = false;
		} else {
			fromYear = date.year;
			fromMonth = date.month;
			fromDay = date.day;
			fromIsLeaf = date.isLeaf;
			toYear = this.year;
			toMonth = this.month;
			toDay = this.day;	
			toIsLeaf = this.isLeaf;
			
			needRevert = true;
		}

		int diffDays = 0;
		int[] daysOfMonth = null;
		
		if (toYear > fromYear) {
			for (int i = fromYear + 1; i < toYear; i++) {
				diffDays += getDaysOfYear(i);
			}
			for (int i = 1; i < toMonth; i++) {
				daysOfMonth = getDaysOfMonth(toYear, i);
				diffDays += daysOfMonth[0];
				if (daysOfMonth.length > 1) {
					diffDays += daysOfMonth[1];
				}
			}
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			if (toIsLeaf) {
				diffDays += daysOfMonth[0];
			}
			diffDays += toDay;
			
			toYear = fromYear;
			toMonth = 12;
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			toIsLeaf = daysOfMonth.length > 1;
			toDay = toIsLeaf ? daysOfMonth[1] : daysOfMonth[0];
		}
		
		if (toMonth > fromMonth) {
			for (int i = fromMonth + 1; i < toMonth; i++) {
				daysOfMonth = getDaysOfMonth(toYear, i);
				diffDays += daysOfMonth[0];
				if (daysOfMonth.length > 1) {
					diffDays += daysOfMonth[1];
				}
			}
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			if (toIsLeaf) {
				diffDays += daysOfMonth[0];
			}
			diffDays += toDay;
			
			toMonth = fromMonth;
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			toIsLeaf = daysOfMonth.length > 1;
			toDay = toIsLeaf ? daysOfMonth[1] : daysOfMonth[0];
		}
		
		daysOfMonth = getDaysOfMonth(toYear, toMonth);
		if (fromIsLeaf == toIsLeaf) {
			diffDays += (toDay - fromDay);
		} else {
			if (fromIsLeaf == false && toIsLeaf == true) {
				diffDays += (daysOfMonth[0] - fromDay);
				diffDays += toDay;
			} else {
				// 발생할 수 없음 - 만약에 발생한다면 로직상에 문제가 있는것임.
				throw new RuntimeException("이 에러는 발생할 수 없습니다.");
			}
		}
        return needRevert ? 0 - diffDays : diffDays;
	}

	public static int getDaysOfYear(int year) {
    	return getDaysOfYearByIndex(year - LUNA_BEGIN_YEAR);
    }
	
	 public static int getDaysOfYearByIndex(int yearIndex) {
    	int daysOfYear = 0;
    	int[] daysOfMonth = null;
    	for (int i = 0; i < 12; i++) {
    		daysOfMonth = getDaysOfMonthByIndex(yearIndex, i);
    		daysOfYear += daysOfMonth[0];
    		if (daysOfMonth.length > 1) {
    			daysOfYear += daysOfMonth[1];
    		}
    	}
    	return daysOfYear;
	 }
	 public static int[] getDaysOfMonthByIndex(int yearIndex, int monthIndex) {
	    return DAYS_OF_MONTH[LUNA_MONTH_TABLE[yearIndex][monthIndex] - 1];
	 }
	 
	 public static int[] getDaysOfMonth(int year, int month) {
	    	return getDaysOfMonthByIndex(year - LUNA_BEGIN_YEAR, month - 1);
	 }


	 
	 
}
